﻿#region Using
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Text;
#endregion // System

namespace Mvp.Library
{
    public class Configuration : DynamicObject
    {
        #region Constructors
        /**********************************************************************
         * Constructors
         *********************************************************************/
        private Configuration() { }
        #endregion // Constructors

        #region Variables
        /**********************************************************************
         * Variables
         *********************************************************************/
        private static Entities.Context.MvpContext _Context = new Entities.Context.MvpContext();
        #endregion // Variables

        #region Properties
        /**********************************************************************
         * Properties
         *********************************************************************/
        private Entities.Configuration ConfigurationData { get; set; }

        #region Read Only Properties
        public int Id
        {
            get { return this.ConfigurationData != null ? this.ConfigurationData.Id : 0; }
        }// of property Id

        public string Name
        {
            get { return this.ConfigurationData != null ? this.ConfigurationData.Name : string.Empty; }
        }// of property Name

        public string Description
        {
            get { return this.ConfigurationData != null ? this.ConfigurationData.Description : string.Empty; }
        }// of property Description

        private bool IsValid
        {
            get
            {
                return this.ConfigurationData != null
                    && this.ConfigurationData.IsActive
                    && this.ConfigurationData.Settings != null
                    && this.ConfigurationData.Settings.Count > 0;
            }
        }// of property IsValid

        private static Entities.Context.MvpContext Context
        {
            get
            {
                if (Configuration._Context == null) { Configuration._Context = new Entities.Context.MvpContext(); }
                return Configuration._Context;
            }
        }// of property Context
        #endregion // Read Only Properties
        #endregion // Properties

        #region Business Methods
        /**********************************************************************
         * Business Methods
         *********************************************************************/
        private void Refresh(int? configurationId = null)
        {
            int id = configurationId.HasValue ? configurationId.Value 
                        : (this.ConfigurationData != null ? this.ConfigurationData.Id : 0);

            if (id != 0)
            {
                this.ConfigurationData = Configuration.Context.Configurations.Find(id);
            }// if the id is set
        }// of Refresh(int?)

        public bool AddSetting<T>(string name, T value, bool searchForSetting = true)
        {
            bool toReturn = false;

            if (!string.IsNullOrWhiteSpace(name))
            {
                // Try to find a setting if it already exists
                Entities.ConfigurationSetting setting = null;

                if (searchForSetting)
                {
                    setting = (from s in Configuration.Context.ConfigurationSettings
                               where s.Configuration.Id == this.ConfigurationData.Id
                                  && string.Compare(s.Name, name, true) == 0
                               select s).FirstOrDefault();
                }// if we're to search for the setting

                if (setting == null)
                {
                    setting = new Entities.ConfigurationSetting()
                    {
                        Configuration = this.ConfigurationData,
                    };
                    Configuration.Context.ConfigurationSettings.Add(setting);
                }// if this is new
                
                setting.Name = name;
                setting.Value = value != null ? value.ToString() : "NULL";
                setting.Type = typeof(T).ToString();
                setting.IsActive = true;

                Configuration.Context.SaveChanges();

                this.Refresh();
                
                toReturn = true;
            }// if the value is set

            return toReturn;
        }// of bool AddSetting<T>(T)

        public bool UpdateSetting<T>(string name, T value, bool isActive = true)
        {
            bool toReturn = false;

            if (!string.IsNullOrWhiteSpace(name)
                && this.IsValid)
            {
                Entities.ConfigurationSetting setting = null;
                setting = (from s in this.ConfigurationData.Settings
                            where string.Compare(name, s.Name, true) == 0
                            select s).FirstOrDefault();

                if (setting != null)
                {
                    setting.Value = value.ToString();
                    setting.Type = typeof(T).ToString();
                    setting.IsActive = isActive;

                    Configuration.Context.SaveChanges();

                    return toReturn;
                }// if the setting is set
                else
                {
                    toReturn = this.AddSetting<T>(name, value, searchForSetting:false);
                }// if the setting isn't set
            }// if the name is set, and we have settings

            return toReturn;
        }// of bool UpdateSetting<T>(string, T)
        #endregion // Business Methods

        #region Overridden Methods
        /**********************************************************************
         * Overridden Methods
         *********************************************************************/
        /// <summary>
        /// Returns the enumeration of all dynamic member names.
        /// </summary>
        /// <returns>
        /// A sequence that contains dynamic member names.
        /// </returns>
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            IEnumerable<string> toReturn = null;
            if (this.IsValid)
            {
                List<string> names = new List<string>();
                foreach (Entities.ConfigurationSetting setting in this.ConfigurationData.Settings.Where(s => s.IsActive))
                {
                    names.Add(setting.Name);
                }// for each value
                toReturn = names;
            }// if the config info is set
            else
            {
                toReturn = base.GetDynamicMemberNames();
            }// if the config info isn't set
            return toReturn;
        }// of IEnumerable<string> GetDynamicMemberNames()

        /// <summary>
        /// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject"/> class can override this method to specify dynamic behavior for operations such as getting a value for a property.
        /// </summary>
        /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject"/> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param>
        /// <param name="result">The result of the get operation. For example, if the method is called for a property, you can assign the property value to <paramref name="result"/>.</param>
        /// <returns>
        /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a run-time exception is thrown.)
        /// </returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            bool toReturn = false;
            result = null;
            if (this.IsValid)
            {
                Entities.ConfigurationSetting setting = (from s in this.ConfigurationData.Settings
                                                            where string.Compare(binder.Name, s.Name, true) == 0
                                                                && s.IsActive
                                                            select s).FirstOrDefault();

                if (setting != null)
                {
                    Type type = System.Type.GetType(setting.Type);
                    TypeConverter converter = TypeDescriptor.GetConverter(type);
                    result = converter.ConvertFromInvariantString(setting.Value);
                    toReturn = true;
                }// if there is a value of this name
                else
                {
                    toReturn = base.TryGetMember(binder, out result);
                }// if there isn't a value with this name
            }// if there are config values
            else
            {
                toReturn = base.TryGetMember(binder, out result);
            }// if there aren't any config values

            return toReturn;
        }// of bool TryGetMember(GetMemberBinder binder)
        #endregion // Overridden Methods

        #region Factory Methods
        /**********************************************************************
         * Factory Methods
         *********************************************************************/
        public static Configuration Load(int configurationId, Entities.Context.IContextInfo contextInfo = null)
        {
            Configuration.Context.SetContextInfo(contextInfo);
            Configuration toReturn = new Configuration();
            toReturn.Refresh(configurationId);
            return toReturn;
        }// of Load(int)

        public static Configuration Load(string configurationName, Entities.Context.IContextInfo contextInfo = null)
        {
            Configuration.Context.SetContextInfo(contextInfo);
            Configuration toReturn = new Configuration();
            toReturn.ConfigurationData = Configuration.Context.Configurations.Where(c => string.Compare(c.Name, configurationName, true) == 0).First();
            return toReturn;
        }// of Load(int)

        public static Configuration Add(string name, string description = "", Entities.Context.IContextInfo contextInfo = null)
        {
            Configuration.Context.SetContextInfo(contextInfo);
            Configuration toReturn = new Configuration();
            Entities.Configuration config = new Entities.Configuration()
            {
                Name = name,
                Description = description,
                IsActive = true
            };
            toReturn.ConfigurationData = Configuration.Context.Configurations.Add(config);
            Configuration.Context.SaveChanges();
            return toReturn;
        }// of Configuration
        #endregion // Factory Methods
    }// of class Configuration
}// of namespace Mvp.Library